package structure

import (
	"fmt"
	"runtime/debug"

	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/serialize"
)

func (execute *Execute) addSchedule(conn SafeConn, info *ScheduleInfo, sid string, time int64) {
	info.Time = time
	scheduleData := NewBaseData(ScheduleType, sid)
	slot := serialize.GetSlot()
	if err := slot.Set(info); err != nil {
		fmt.Println("sch add err", err)
	}
	b := slot.Bytes()
	conn.AutoRelease(slot)
	execute.CreateBytes(conn, scheduleData, b, 86400)
}

func (execute *Execute) GenerateSchedule(mapNoId string, from Data, target Data, key string, time int64, schedule ScheduleLogic, b []byte, params ParamSlice, delay int) *ScheduleInfo {
	info := &ScheduleInfo{
		MapNoId:  mapNoId,
		Schedule: schedule,
		From:     NewBaseDataFrom(from),
		Target:   NewBaseDataFrom(target),
		Key:      key,
		Time:     time,
		Data:     b,
		Params:   params,
	}
	return info
}

func (execute *Execute) AddSchedule(conn SafeConn, info *ScheduleInfo) {
	sid, old := execute.getScheduleIn(conn, info.MapNoId, info.Target, info.Key)
	if old != nil {
		info.Version = old.Version + 1
	}
	info.ScheduleData = BaseData{
		DataType: ScheduleType,
		Id:       sid,
	}
	execute.addSchedule(conn, info, sid, info.Time)
}

func (execute *Execute) PushSchedule(conn SafeConn, info *ScheduleInfo) {
	sid, old := execute.getScheduleIn(conn, info.MapNoId, info.Target, info.Key)

	if old != nil {
		// time如果为延迟，则得到实际时间，format后不变
		// time如果为实际时间，则得到延迟，format得到实际时间
		if info.Time == -1 {
			info.Time = old.Time
		} else {
			info.Time = execute.FormatTimeFrom(info.Time, old.Time)
		}
		info.Version = old.Version + 1
	}

	info.ScheduleData = BaseData{
		DataType: ScheduleType,
		Id:       sid,
	}
	execute.addSchedule(conn, info, sid, info.Time)
	//fmt.Println("push", sid, info)
	if err := conn.Send("ZADD", execute.Key+"/schedule/"+info.MapNoId, info.Time-int64(info.ScheduleDelay), sid); err != nil {
		fmt.Println("ZADD", err)
	}
}

func (execute *Execute) UpdateSchedule(conn SafeConn, info *ScheduleInfo, time int64) {
	var sid string
	var old *ScheduleInfo
	key := info.Key
	if info.Depend != "" {
		key = info.Depend
	}
	sid, old = execute.getScheduleIn(conn, info.MapNoId, info.Target, key)
	if info.Depend != "" {
		sid = execute.GetScheduleId(conn, info.MapNoId, info.Target, info.Key)
	}
	if old != nil {
		if old.Version != info.Version {
			debug.PrintStack()
			fmt.Println("version", old.Version, info.Version, old.Version == info.Version, info.Target)
			// 计划更新，放弃内部逻辑
			return
		}
	}
	execute.addSchedule(conn, info, sid, time)
	//if old != nil {
	//fmt.Println("update", sid, info.Target, info.Key, time, execute.CurrentTime(), time-execute.CurrentTime(), old)
	//}
	if err := conn.Send("ZADD", execute.Key+"/schedule/"+info.MapNoId, time-int64(info.ScheduleDelay), sid); err != nil {
		fmt.Println("ZADD", err)
	}
}

func (execute *Execute) getSchedule(conn SafeConn, sid string) *ScheduleInfo {
	scheduleData := BaseData{
		DataType: ScheduleType,
		Id:       sid,
	}
	m := execute.GetBytes(conn, &scheduleData)
	if m == nil || len(m) == 0 {
		return nil
	}
	info := &ScheduleInfo{}
	_, _ = info.UnmarshalMsg(m)
	info.ScheduleData = scheduleData
	return info
}
func (execute *Execute) getAndDelSchedule(conn SafeConn, sid ...string) []*ScheduleInfo {
	l := len(sid)
	if l == 0 {
		return nil
	}
	if l == 1 {
		scheduleData := BaseData{
			DataType: ScheduleType,
			Id:       sid[0],
		}
		m := execute.GetAndDelBytes(conn, &scheduleData)
		info := &ScheduleInfo{}
		_, _ = info.UnmarshalMsg(m)
		info.ScheduleData = scheduleData
		return []*ScheduleInfo{info}
	}
	scheduleData := make([]Data, len(sid))
	for index, s := range sid {
		scheduleData[index] = &BaseData{
			DataType: ScheduleType,
			Id:       s,
		}
	}
	m := execute.GetAndDelBytesBatch(conn, scheduleData...)
	if m == nil || len(m) == 0 {
		return nil
	}
	info := make([]*ScheduleInfo, len(m))
	for index, b := range m {
		v := &ScheduleInfo{}
		_, err := v.UnmarshalMsg(b)
		if err != nil {
			fmt.Println("sch get", err, index, len(scheduleData), m, scheduleData[index])
		}
		v.ScheduleData = *(scheduleData[index].(*BaseData))
		info[index] = v
	}

	return info
}
func (execute *Execute) GetScheduleId(conn SafeConn, mapNoId string, data Data, key string) string {
	return execute.GetDataType(data.GetDataType()) + "/" + mapNoId + "/" + utils.ToString(data.GetId()) + "/" + key
}

func (execute *Execute) getScheduleIn(conn SafeConn, mapNoId string, data Data, key string) (string, *ScheduleInfo) {
	sid := execute.GetScheduleId(conn, mapNoId, data, key)
	return sid, execute.getSchedule(conn, sid)
}

func (execute *Execute) ResetSchedule(conn SafeConn, mapNoId string, data Data, key string, time int64, delay int) {
	sid, info := execute.getScheduleIn(conn, mapNoId, data, key)
	if info == nil {
		return
	}
	version := info.Version
	//resetTimes := 0
	time = execute.FormatTime(time)
	info.Version = version + 1
	info.Time = time

	execute.addSchedule(conn, info, sid, time)
	//info.resetTimes = resetTimes
	//sKey := execute.Update(conn, &info.ScheduleData, map[string]interface{}{
	//	"version":    version + 1,
	//	"time":       time,
	//	"resetTimes": resetTimes,
	//})
	//if err := conn.Send("EXPIREAT", sKey, int(time/1000+60)); err != nil {
	//	fmt.Println("EXPIREAT", err)
	//}
	_ = conn.Send("ZADD", execute.Key+"/schedule/"+info.MapNoId, time-int64(delay), sid)
}
func (execute *Execute) ChangeParamSchedule(conn SafeConn, mapNoId string, data Data, key string, value ParamSlice) {
	_, info := execute.getScheduleIn(conn, mapNoId, data, key)
	if info == nil {
		return
	}

	params, _ := value.MarshalMsg(nil)
	_ = execute.Update(conn, &info.ScheduleData, map[string]interface{}{
		"params": params,
	})
}

func (execute *Execute) HoldSchedule(conn SafeConn, mapNoId string, data Data, key string, d []byte) {
	_, info := execute.getScheduleIn(conn, mapNoId, data, key)
	if info == nil {
		return
	}
	version := info.Version
	_ = execute.Update(conn, &info.ScheduleData, map[string]interface{}{
		"data":    d,
		"version": version + 1,
	})
}

func (execute *Execute) RemoveSchedule(conn SafeConn, mapNoId string, baseData Data, key string) {
	sid := execute.GetScheduleId(conn, mapNoId, baseData, key)
	//fmt.Println("remove", sid)
	r, err := conn.Do("ZREM", execute.Key+"/schedule/"+mapNoId, sid)
	if err != nil {
		return
	}
	if utils.ToInt(r) == 0 {
		return
	}
	execute.Delete(conn, &BaseData{
		DataType: ScheduleType,
		Id:       sid,
	})
}

func (execute *Execute) PopSchedule(conn SafeConn, mapNoId string, max int64, number int) []*ScheduleInfo {
	r, err := conn.Do("EVAL", "local v1 = redis.pcall(\"ZRANGEBYSCORE\", KEYS[1], ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5])\nlocal v2 = 0\nif (table.getn(v1) > 0)\nthen\nv2 = redis.pcall(\"ZREM\", KEYS[1], unpack(v1))\nend\nreturn {v1, v2}", 1, execute.Key+"/schedule/"+mapNoId, "-inf", max, "LIMIT", 0, number)
	if err != nil {
		fmt.Println("ZRANGEBYASCORE", err)
		return nil
	}
	if r == nil {
		return nil
	}
	rr := r.([]interface{})
	if len(rr) < 1 {
		//fmt.Println("get", 2, rr)
		return nil
	}
	//fmt.Println("get", 2, rr)
	if utils.ToInt(rr[1]) < 1 {
		//zc, _ := conn.Do("ZCOUNT", execute.Key+"/schedule/"+mapNoId, 0, -1)
		//fmt.Println("schedule miss", sid, zc, execute.CurrentTime())
		return nil
	}
	sid := utils.ToStringSlice(rr[0])
	//fmt.Println("get", r)
	//fmt.Println(sid, utils.ToString(rr[1]))
	//fmt.Println("get", "zrem", cr)
	info := execute.getAndDelSchedule(conn, sid...)
	//fmt.Println("schedule", info[0].Target, info[0].Key)
	if info == nil {
		//zc, _ := conn.Do("ZCOUNT", execute.Key+"/schedule/"+mapNoId, 0, -1)
		//fmt.Println("schedule info miss", sid, zc, execute.CurrentTime())
		return nil
	}
	return info
}
